package flyweight

import (
	"fmt"
	"math/rand"
	"reflect"
	"strconv"
	"sync"
)

// Flyweight 享元接口，通过这个接口享元可以接收并作用于外部状态
type Flyweight interface {
	// Operator 示例操作，传入外部状态
	Operator(extrinsicState string)
}

// ConcreteFlyweight 享元对象
type ConcreteFlyweight struct {
	intrinsicState string // 示例，描述内部状态
}

// NewConcreteFlyweight 构造方法，传入享元对象的内部状态
func NewConcreteFlyweight(state string) *ConcreteFlyweight {
	return &ConcreteFlyweight{
		intrinsicState: state,
	}
}

func (c *ConcreteFlyweight) Operator(extrinsicState string) {
	// 具体的功能处理，可能会用到享元内部、外部的状态
	flag := c.intrinsicState == extrinsicState
	if flag {
		fmt.Println("c.intrinsicState == extrinsicState")
	} else {
		fmt.Println("c.intrinsicState != extrinsicState")
	}
}

// getState 根据需要，可以提供外部访问内部数据的getter方法，但不会提供setter方法
// 也就是这些数据不会让外部来修改
func (c *ConcreteFlyweight) getState() string {
	return c.intrinsicState
}

// UnsharedConcreteFlyweight 不需要共享的flyweight对象
// 通常是将被共享的享元对象作为子结点组合出来的对象
type UnsharedConcreteFlyweight struct {
	allState string // 示例，描述对象的状态
}

func (u *UnsharedConcreteFlyweight) Operator(extrinsicState string) {
	// 具体的功能处理，可能会用到享元内部、外部的状态
}

var (
	flyweightFactoryIns *flyweightFactory
	onceFactory         sync.Once
)

func GetFlyweightFactory() *flyweightFactory {
	onceFactory.Do(func() {
		flyweightFactoryIns = &flyweightFactory{
			fsMap: make(map[string]Flyweight),
		}
	})
	return flyweightFactoryIns
}

// flyweightFactory 享元工厂
type flyweightFactory struct {
	// 缓存多个Flyweight对象，这里只是示意一下
	fsMap map[string]Flyweight
}

// GetFlyweight 获取key对应的享元对象
func (f *flyweightFactory) GetFlyweight(key string) Flyweight {
	// 这个方法中基本的实现步骤如下：
	// 1：先从缓存中查找，是否存在key对应的Flyweight对象
	// 2：如果存在，就返回相对应的Flyweight对象
	fs, ok := f.fsMap[key]
	if !ok {
		// 3:如果不存在
		// 3.1：创建一个新的Flyweight对象
		fs = NewConcreteFlyweight(key)
		// 3.2：把这个新的Flyweight对象添加到缓存中
		f.fsMap[key] = fs
		// 3.3：然后返回这个新的Flyweight对象
	}
	return fs
}

var (
	clientInstance *client
	onceClient     sync.Once
)

// Client 对象，通常会维持一个对Flyweight的引用
// 计算或存储一个或多个Flyweight的外部状态
type client struct {
	mp map[string][]Flyweight
}

func GetClient() *client {
	onceClient.Do(func() {
		clientInstance = &client{
			mp: make(map[string][]Flyweight),
		}
	})
	return clientInstance
}

func (c *client) query(key string) []Flyweight {
	var ret []Flyweight
	for i := 0; i < 10; i++ {
		fm := GetFlyweightFactory().GetFlyweight(key + strconv.Itoa(rand.Intn(3)))
		ret = append(ret, fm)
	}
	return ret
}

func (c *client) Process(key string) {
	ret := c.query(key)
	c.mp[key] = ret
}

func (c *client) Print(key string) {
	for i, _ := range c.mp[key] {
		fmt.Println(reflect.ValueOf(c.mp[key][i]).Pointer())
	}
	fmt.Println()
}
